package com.wmx.thread.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**********************************
 * @author wmx
 * @date 2019/8/22 14:59
 * @description:
 * @version 1.0
 *
 * CAS是什么？
 * CAS是英文单词CompareAndSwap的缩写，中文意思是：比较并替换。CAS需要有3个操作数：内存地址V，旧的预期值A，即将要更新的目标值B。
 * CAS指令执行时，当且仅当内存地址V的值与预期值A相等时，将内存地址V的值修改为B，否则就什么都不做。整个比较并替换的操作是一个原子操作。
 * 在java.util.concurrent.atomic包 。Java并发包中的很多类都使用了CAS技术
 *
 * ABA问题是什么？
 * ABA就是CAS算法中在多线线程操作中出现的错误。
 *
 * 　 提到了CAS操作存在问题，就是在CAS之前A变成B又变回A，CAS还是能够设置成功的，什么场景下会出现这个问题呢？查了一些资料，发现在下面的两种情况下会出现ABA问题。
 *　  1.A最开始的内存地址是X，然后失效了，有分配了B，恰好内存地址是X，这时候通过CAS操作，却设置成功了
 *
 *　　这种情况在带有GC的语言中，这种情况是不可能发生的，为什么呢？拿JAVA举例，在执行CAS操作时，A，B对象肯定生命周期内，GC不可能将其释放，
 *    那么A指向的内存是不会被释放的，B也就不可能分配到与A相同的内存地址，CAS失败。若在无GC的，A对象已经被释放了，那么B被分配了A的内存，CAS成功。

 *　　2.线程1准备用CAS将变量的值由A替换为B，在此之前，线程2将变量的值由A替换为C，又由C替换为A，然后线程1执行CAS时发现变量的值仍然为A，所以CAS成功。
 *    但实际上这时的现场已经和最初不同了，尽管CAS成功，但可能存在潜藏的问题。比如：
 *
 *　　现有一个用单向链表实现的堆栈，栈顶为A，这时线程T1已经知道A.next为B，然后希望用CAS将栈顶替换为B：head.compareAndSet(A,B);在T1执行上面这条指令之前，线程T2介入，
 *    将A、B出栈，再pushD、C、A。而对象B此时处于游离状态：此时轮到线程T1执行CAS操作，检测发现栈顶仍为A，所以CAS成功，栈顶变为B，但实际上B.next为null，
 *    其中堆栈中只有B一个元素，C和D组成的链表不再存在于堆栈中，平白无故就把C、D丢掉了。

 *　　以上就是由于ABA问题带来的隐患，各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记，避免并发操作带来的问题，
 *    在Java中，AtomicStampedReference<E>也实现了这个作用，它通过包装[E,Integer]的元组来对对象标记版本戳stamp，从而避免ABA问题，
 *    例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新，AtomicInteger会成功执行CAS操作，
 *    而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败
 *
 ************************************/
public class ABA {
    private static AtomicInteger atomicInteger  = new AtomicInteger(100);

    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,0);

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = atomicInteger.compareAndSet(100,101);  // 更新成功

            System.out.println(b);


        });

        Thread thread2 = new Thread(()->{
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);

        });

        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

      /*   测试AtomicStampedReference*/

        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedReference.compareAndSet(100, 101,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1); //stamp 1

                atomicStampedReference.compareAndSet(101, 100,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1); // stamp 2
            }
        });

        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedReference.getStamp();
                System.out.println("before sleep : stamp = " + stamp);    // stamp = 0
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("after sleep : stamp = " + atomicStampedReference.getStamp());//stamp = 2
                boolean c3 = atomicStampedReference.compareAndSet(100, 101, stamp, stamp+1);
                System.out.println(c3);        //false
            }
        });

        refT1.start();
        refT2.start();


    }

}
